package com.jsyso.weixin.web;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import com.jsyso.jsyso.json.jackson.JsonMapper;
import com.jsyso.jsyso.spring.Springs;
import com.jsyso.jsyso.util.JsMap;
import com.jsyso.jsyso.web.JHttpServlet;
import com.jsyso.jsyso.web.WebUtils;
import com.jsyso.weixin.handler.RequestHandler;
import com.jsyso.weixin.msg.MsgType;
import com.jsyso.weixin.msg.in.EventType;
import com.jsyso.weixin.msg.in.InMsg;
import com.jsyso.weixin.msg.in.InTextMsg;
import com.jsyso.weixin.msg.in.UnknownInMsg;
import com.jsyso.weixin.msg.in.event.InFollowEvent;
import com.jsyso.weixin.msg.in.event.InMenuEvent;
import com.jsyso.weixin.msg.in.event.InQrCodeEvent;
import com.jsyso.weixin.msg.in.event.InUnknownEvent;
import com.jsyso.weixin.msg.out.OutMsg;
import com.jsyso.weixin.msg.out.OutUnknownMsg;
import com.jsyso.weixin.utils.SignatureUtils;
import com.jsyso.weixin.utils.XML;

/**
 * 微信统一入口
 * @author janjan, xujian_jason@163.com
 *
 */
@SuppressWarnings({"rawtypes", "unchecked" })
public class WeixinServlet extends JHttpServlet {
	private static final long serialVersionUID = 1L;
	protected static final String KEY_ENCODINGAESKEY 	= "encodingAesKey";
	protected static final String KEY_APPSECRET 		= "appSecret";
	protected static final String KEY_TOKEN 			= "token";
	// 处理器
	private Map<String, RequestHandler> handlerMap 
	= new ConcurrentHashMap<String, RequestHandler>();
	// 当前appid配置项
	private JsMap currentAppidConfig;
	
	@Override
	protected void initServlet(ServletConfig config) {
		// 初始化配置
		JsMap params = getInitParams();
		for(String key : params.keySet()) {
			String configJson = params.get(key, String.class);
			JsMap configMap = JsonMapper.getInstance().fromJson(configJson, JsMap.class);
			InitParams.putConfig(key, configMap);
		}
		// 解析消息处理器
		Map<String, RequestHandler> beans = Springs.getBeansOfType(RequestHandler.class);
		for(RequestHandler handler : beans.values()) {
			Type[] types = handler.getClass().getGenericInterfaces();
			// 兼容实现多接口
			for(Type type : types) {
				ParameterizedType pType = null;
				if(type instanceof ParameterizedType 
						&& (pType = (ParameterizedType)type).getRawType() == RequestHandler.class) {
					Type actType = pType.getActualTypeArguments()[0];
					if(logger.isDebugEnabled()) {
						logger.debug("[解析消息处理器]type = " + actType.getTypeName());
					}
					handlerMap.put(((Class<?>)actType).getName(), handler);
				}
			}
//			Type type = ((ParameterizedType)handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
//			handlerMap.put(((Class<?>)type).getName(), handler);
		}
	}
	
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) {
		// 通过传递appid参数支持多公众号
		String appid = WebUtils.getParam(request, "appid", null, String.class);
		if(StringUtils.isBlank(appid)) {
			return ;
		}
		// 设置当前appid配置项
		currentAppidConfig = getConfigByAppid(appid, request);
		// 是否是第一次配置服务器
		if(isConfigServer(request)) {
			write(doConfigService(request), response);
			return ;
		}
		// 获取request内容
		String xmlContent = readContent(request);
		if(StringUtils.isBlank(xmlContent)) {
			throw new RuntimeException("请不要在浏览器中请求该连接");
		}
		if(logger.isDebugEnabled()) {
			logger.debug("[接收到的内容，appid="+appid+"]" + xmlContent);
		}
		// 构建XML对象
		XML xml = XML.create(xmlContent);
		// 执行解析
		InMsg inMsg = doParse(xml);
		if(logger.isDebugEnabled()) {
			logger.debug("[解析内容得到输入消息]" + inMsg.getClass().getName());
		}
		inMsg.setAppid(appid);
		// 处理输入消息
		RequestHandler handler = handlerMap.get(inMsg.getClass().getName());
		if(logger.isDebugEnabled()) {
			logger.debug("[处理器处理]" + (handler == null ? "空处理器" : handler.getClass().getName()));
		}
		OutMsg outMsg = null;
		if(handler != null) 
			outMsg = handler.onHandle(inMsg);
		if(outMsg == null)
			outMsg = new OutUnknownMsg();
		// 输出消息
		if(logger.isDebugEnabled()) {
			logger.debug("[输出消息]" + outMsg.toString());
		}
		write(outMsg.toString(), response);
	}
	
	/**
	 * 输出内容
	 * @param content
	 * @param response
	 */
	protected void write(String content, HttpServletResponse response) {
		try {
			PrintWriter pw = response.getWriter();
			pw.write(content);
	        pw.flush();
	        pw.close();
		} catch (Exception e) {
			throw new RuntimeException("输出数据异常：" + e.getMessage(), e);
		}
	}
	
	/**
	 * 根据appid获取配置项
	 * @param appid
	 * @param request
	 * @return
	 */
	protected JsMap getConfigByAppid(String appid, HttpServletRequest request) {
		return InitParams.getConfig(appid);
	}
	
	/**
	 * 是否是配置服务器
	 * @param request
	 * @return
	 */
	protected boolean isConfigServer(HttpServletRequest request) {
		return StringUtils.isNotBlank(WebUtils.getParam(request, "echostr", null, String.class));
	}
	
	/**
	 * 处理微信配置
	 * @param request
	 * @param response
	 * @return
	 */
	protected String doConfigService(HttpServletRequest request) {
		String echostr = WebUtils.getParam(request, "echostr", "", String.class);
		String signature = WebUtils.getParam(request, "signature", "", String.class);
		String timestamp = WebUtils.getParam(request, "timestamp", "", String.class);
		String nonce = WebUtils.getParam(request, "nonce", "", String.class);
		String token = currentAppidConfig.get(KEY_TOKEN, String.class);
		boolean pass = SignatureUtils.checkSignature(token, signature, timestamp, nonce);
		return pass ? echostr : StringUtils.EMPTY;
	}
	
	/**
	 * 读取请求内容
	 * @param request
	 * @return
	 */
	protected String readContent(HttpServletRequest request) {
		try {
			StringBuilder builder = new StringBuilder();
			BufferedReader br = request.getReader();
			for (String line=null; (line=br.readLine())!=null;) {
				builder.append(line).append("\n");
			}
			IOUtils.closeQuietly(br);
			return builder.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 解析输入消息
	 * @param request
	 * @param response
	 */
	protected InMsg doParse(XML xml) {
		String toUserName = xml.getString("//ToUserName");
        String fromUserName = xml.getString("//FromUserName");
        Integer createTime = xml.getNumber("//CreateTime").intValue();
        String msgType = xml.getString("//MsgType");
        if(MsgType.EVENT.getType().equals(msgType)) 
        	return parseInEvent(xml, toUserName, fromUserName, createTime, MsgType.EVENT);
        if(MsgType.TEXT.getType().equals(msgType))
        	return parseInTextMsg(xml, toUserName, fromUserName, createTime, MsgType.TEXT);
        return new UnknownInMsg(toUserName, fromUserName, createTime, MsgType._UNKNOWN);
	}
	
	private InMsg parseInEvent(XML xml, String toUserName, 
			String fromUserName, Integer createTime, MsgType msgType) {
		String event = xml.getString("//Event");
        String eventKey = xml.getString("//EventKey");
        if(logger.isDebugEnabled()) {
			logger.debug("[解析事件]event="+event+"，eventKey="+eventKey);
		}
        if(EventType.UNSUBSCRIBE.getType().equals(event)) {
        	return new InFollowEvent(toUserName, fromUserName, createTime, msgType, EventType.UNSUBSCRIBE);
        }
        String ticket = xml.getString("//Ticket");
        if(EventType.SUBSCRIBE.getType().equals(event) 
        		&& StringUtils.isNotBlank(eventKey) && eventKey.startsWith("qrscene_")) {
        	InQrCodeEvent inQrCodeEvent = new InQrCodeEvent(toUserName, fromUserName, createTime, msgType, EventType.SUBSCRIBE);
        	inQrCodeEvent.setEventKey(eventKey);
        	inQrCodeEvent.setTicket(ticket);
        	return inQrCodeEvent;
        }
        if(EventType.SCAN.getType().equals(event)) {
        	InQrCodeEvent inQrCodeEvent = new InQrCodeEvent(toUserName, fromUserName, createTime, msgType, EventType.SCAN);
        	inQrCodeEvent.setEventKey(eventKey);
        	inQrCodeEvent.setTicket(ticket);
        	return inQrCodeEvent;
        }
        if(EventType.SUBSCRIBE.getType().equals(event)) {
        	return new InFollowEvent(toUserName, fromUserName, createTime, msgType, EventType.SUBSCRIBE);
        }
        if(EventType.CLICK.getType().equals(event)) {
        	InMenuEvent inMenuEvent = new InMenuEvent(toUserName, fromUserName, createTime, msgType, EventType.CLICK);
        	inMenuEvent.setEventKey(eventKey);
        	return inMenuEvent;
        }
        if(EventType.VIEW.getType().equals(event)) {
        	InMenuEvent inMenuEvent = new InMenuEvent(toUserName, fromUserName, createTime, msgType, EventType.VIEW);
        	inMenuEvent.setEventKey(eventKey);
        	return inMenuEvent;
        }
        return new InUnknownEvent(toUserName, fromUserName, createTime, msgType, EventType._UNKNOWN);
	}
	
	private InMsg parseInTextMsg(XML xml, String toUserName, String fromUserName, Integer createTime, MsgType msgType) {
        InTextMsg msg = new InTextMsg(toUserName, fromUserName, createTime, msgType);
        msg.setContent(xml.getString("//Content"));
        msg.setMsgId(xml.getString("//MsgId"));
        return msg;
    }

	@Override
	protected boolean isSetter() {
		return false;
	}
	
}
